package OS.experiment12;

import java.util.Arrays;
import java.util.Date;
import java.util.Random;
import java.util.Scanner;
import java.util.concurrent.LinkedBlockingQueue;

public class Process {

    //就绪队列
    private LinkedBlockingQueue<PCB> readyQueue;

    //正在运行的进程
    private PCB running;

    //阻塞队列
    private LinkedBlockingQueue<PCB> blockQueue;

    //就绪队列是否有变化，用于优先级优先算法是否需要重新排列的依据
    private boolean hasChanged;

    Process(){
        //初始化队列
        readyQueue = new LinkedBlockingQueue<>();
        blockQueue = new LinkedBlockingQueue<>();
    }

    /**
     * 创建原语
     * 就绪队列与阻塞队列中不能存在同名进程，且总数最多为10个进程。
     * @param name          进程名
     * @param priority      优先级
     * @param next          下一个PCB
     * @return              创建结果
     */
    public PCB create(String name, int priority, PCB next) {
        int runningCount = running == null ? 0 : 1;
        if (readyQueue.size() + blockQueue.size() + runningCount >= 10) {
            return null;
        } else if (null != findProcess(name)) {
            return null;
        } else {
            //用随机数表示作业需要运行的时间
            Random random = new Random(new Date().getTime());
            long runTime = random.nextLong();
            //用时间做种子得到的太大了咱们取模一下，让范围在0-10秒之间
            runTime = runTime % 10000;

            PCB newPCB = new PCB(name, priority, Math.abs(runTime), next);
            readyQueue.offer(newPCB);

            //就绪队列改变
            hasChanged = true;
            return newPCB;
        }
    }

    /**
     * 根据用户输入的进程名称查看该进程是否处在运行状态，
     * 若处在运行状态，修改该进程PCB的状态，并将该进程转入阻塞队列；
     * 若该进程处在其它状态，则给出相应的提示信息；
     * 若该进程不存在，也需要给出相应的提示信息。
     * @param name  进程名
     * @return      处理结果
     */
    public int block(String name){
        PCB target = findProcess(name);

        //进程不存在
        if (null == target) return -1;

        //正在运行状态
        if (target.getState() == PCB.STATE_RUN) {
            //若处在运行状态，修改该进程PCB的状态，并将该进程转入阻塞队列；
            running = null;
            target.setState(PCB.STATE_BLOCK);

            blockQueue.offer(target);

            //就绪队列改变
            hasChanged = true;
            return PCB.STATE_RUN;
        }

        //就绪队列改变
        hasChanged = true;
        //其他状态
        return target.getState();
    }

    /**
     * 功能类似阻塞功能，注意：若阻塞队列为空，则不能执行唤醒功能。
     * @param name  进程名
     * @return      返回结果
     */
    public int wakeup(String name){
        //阻塞队列为空则返回失败
        if (blockQueue.size() <= 0) return -1;

        PCB target = findProcess(name);

        //进程不存在
        if (null == target) return -1;

        //阻塞状态
        if (target.getState() == PCB.STATE_BLOCK){
            //那么sunshine小问题：这块为什么可以用remove来移除掉原本队列中的元素呢？
            blockQueue.remove(target);
            target.setState(PCB.STATE_READY);
            readyQueue.offer(target);

            //就绪队列改变
            hasChanged = true;
            return PCB.STATE_BLOCK;
        }

        //就绪队列改变
        hasChanged = true;
        //其他状态
        return target.getState();
    }

    /**
     * 根据用户输入要结束的进程名称，
     * 从就绪队列、阻塞队列和正在运行的进程中找到相应的进程PCB，
     * 并将该PCB从相应队列中删除。若该进程不存在，需要给出提示。
     * @param name  进程名
     * @return      提示
     */
    public int stop(String name) {
        PCB target = findProcess(name);

        if (null == target) return -1;

        switch (target.getState()) {
            case 0: readyQueue.remove(target);break;
            case 1: running = null;break;
            case 2: blockQueue.remove(target);break;
        }

        //就绪队列改变
        hasChanged = true;
        return target.getState();
    }

    /**
     * 找到同名进程
     * @param name  进程名
     * @return      结果
     */
    private PCB findProcess(String name){
        //转换成数组遍历
        PCB[] readyList;
        PCB[] blockList;
        readyList = readyQueue.toArray(new PCB[0]);
        blockList = blockQueue.toArray(new PCB[0]);

        //目标进程
        PCB target = running;

        for (PCB p : readyList){
            //非null后代表找到了则不进行再查找了
            if (null != target) break;
            if (p.getName().equals(name)) target = p;
        }

        for (PCB p : blockList){
            if (null != target) break;
            if (p.getName().equals(name)) target = p;
        }

        return target;
    }

    /**
     * 时间片轮转调度算法
     * 每选择一次该功能，将就绪队列中所有进程的内容显示在屏幕上，
     * 并将每个执行进程的执行时间减去一个时间片，
     * 若某进程的剩余时间小于等于0，则表明该进程运行结束，撤销该进程的PCB。
     */
    public void timeRotation(Callback callback){
        //如果就绪队列不为空，则循环执行
        new Thread(new Runnable() {
            @Override
            public void run() {
                //时间片长度，这里设置一下固定的1000ms
                long timeRotationLength = 1000L;

                while (!readyQueue.isEmpty()) {
                    try{//那么这个try应该放在循环里还是外？

                        //就绪队列出队
                        PCB nowProcess = readyQueue.poll();

                        //模拟运行
                        nowProcess.setState(PCB.STATE_RUN);
                        running = nowProcess;
                        callback.schedule();    //调用回调进行界面显示
                        Thread.sleep(timeRotationLength);

                        //减去时间片
                        nowProcess.setRunTime(nowProcess.getRunTime() - timeRotationLength);

                        //判断是应该入就绪队列还是应该撤销PCB，
                        //并且注意第一个条件，如果在运行中被阻塞，那么不应该继续
                        if (nowProcess.getState() == PCB.STATE_RUN && nowProcess.getRunTime() > 0){
                            nowProcess.setState(PCB.STATE_READY);
                            readyQueue.offer(nowProcess);
                        }else {
                            hasChanged = true;
                        }
                        running = null;
                    }catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                callback.schedule();
            }
        }).start();
    }

    /**
     * 优先级调度算法
     * 选择该功能选项时，系统将从就绪队列中选择优先级最高的进程，
     * 使该进程处于执行状态
     * （将进程PCB的内容显示在屏幕上，显示完成后，该进程结束，即撤销该进程的PCB）。
     */
    public void priorityScheduling(Callback callback){
        if (hasChanged) processSortByPriority();

        //模拟进程运行情况
        new Thread(new Runnable() {
            @Override
            public void run() {
                //系统刷新时间长度，这里设置一下固定的100ms，这样做可以尽可能让系统输出信息稳定
                long freshTimeLength = 100L;
                while (!readyQueue.isEmpty()){
                    try {
                        //就绪队列首个元素，不出队。其实这里应该是不对的，因为正常逻辑是运行即出队，这里为了实现随时停止功能，就用RUN状态表示出队了。需要在读的时候配合不读RUN
                        PCB nowProcess = readyQueue.peek();

                        //先判断下是否需要将就绪队列首个进程移除
                        if (nowProcess.getState() != PCB.STATE_READY || nowProcess.getRunTime() <= 0){
                            readyQueue.poll();
                            hasChanged = true;
                        }

                        //模拟运行
                        nowProcess.setState(PCB.STATE_RUN);
                        running = nowProcess;
                        callback.schedule();    //调用回调进行界面显示
                        Thread.sleep(freshTimeLength);

                        //减去时间片
                        nowProcess.setRunTime(nowProcess.getRunTime() - freshTimeLength);

                        //判断是应该入就绪队列还是应该撤销PCB，
                        //并且注意第一个条件，如果在运行中被阻塞，那么不应该继续
                        if (nowProcess.getState() == PCB.STATE_RUN && nowProcess.getRunTime() > 0){
                            nowProcess.setState(PCB.STATE_READY);
                        }else {
                            hasChanged = true;
                        }
                        running = null;
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
                callback.schedule();
            }
        }).start();
    }

    /**
     * 进程优先级排序
     * PCB已经实现Comparable接口
     */
    private void processSortByPriority(){
        //队列中的PCB转换成数组后进行比较
        PCB[] readyList = readyQueue.toArray(new PCB[0]);
        Arrays.sort(readyList);
        //清空当前队列
        readyQueue.clear();
        for (PCB p : readyList){
            readyQueue.offer(p);
        }
        //排序完成后改变结束
        hasChanged = false;
    }

    public LinkedBlockingQueue<PCB> getReadyQueue() {
        return readyQueue;
    }

    public PCB getRunning() {
        return running;
    }

    public LinkedBlockingQueue<PCB> getBlockQueue() {
        //对多线程导致的数据不统一（即本应是BLOCK状态的而不是的进行设置），虽然增加了获取时间，但是换来了安全
        PCB[] blockList;
        blockList = blockQueue.toArray(new PCB[0]);
        blockQueue.clear();

        for (PCB p : blockList){
            p.setState(PCB.STATE_BLOCK);
            blockQueue.offer(p);
        }
        return blockQueue;
    }
}

/**
 * PCB实现类
 * 实现Comparable实现进程优先级比较
 */
class PCB implements Comparable<PCB>{
    //优先级
    static int PRIORITY_SYSTEM = 1;
    static int PRIORITY_MAIN = 2;
    static int PRIORITY_USER = 3;

    //状态
    static int STATE_READY = 0;
    static int STATE_RUN = 1;
    static int STATE_BLOCK = 2;
    //进程名
    private String name;

    //优先级:1 2 3，数字越高优先级越低
    private int priority;

    //运行需要时间，就是该进程系统预计完成时间，单位：ms
    private long runTime;

    //状态，默认为就绪状态
    private int state = STATE_READY;

    //下一个PCB。用途...
    private PCB next;

    public PCB(String name, int priority, long runTime, PCB next) {
        this.name = name;
        this.runTime = runTime;
        this.priority = priority;

        this.next = next;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPriority() {
        return priority;
    }

    public void setPriority(int priority) {
        this.priority = priority;
    }

    public long getRunTime() {
        return runTime;
    }

    public void setRunTime(long runTime){
        this.runTime = runTime;
    }

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
    }

    public PCB getNext() {
        return next;
    }

    public void setNext(PCB next) {
        this.next = next;
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder();
        result
                .append("进程名称：")
                .append(this.name)
                .append(",进程优先级：")
                .append(this.priority)
                .append(",运行需要时间：")
                .append(this.runTime)
                .append(",运行状态：")
                .append(this.state);
        return result.toString();
    }

    @Override
    public int compareTo(PCB o) {
        return this.priority - o.getPriority();
    }
}
